// Go作者版权所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// Package embed提供对正在运行的Go程序中嵌入的文件的访问。
// None
// Go导入“嵌入”的源文件可以使用
// 要初始化字符串、[]字节或FS类型的变量，请使用
// 编译时从包目录或子目录读取的文件。
// None
// 例如，这里有三种方法可以嵌入名为hello.txt的文件
// 然后在运行时打印其内容。
// None
// 将一个文件嵌入到字符串中：
// None
// 导入“嵌入”
// None
// None
// var s字符串
// 印刷品
// None
// 将一个文件嵌入到字节片中：
// None
// 导入“嵌入”
// None
// None
// 变量b[]字节
// 打印（字符串（b））
// None
// 将一个或多个文件嵌入到文件系统中：
// None
// 导入“嵌入”
// None
// None
// var f embed.FS
// 数据：=f.ReadFile（“hello.txt”）
// 打印（字符串（数据））
// None
// 指令
// None
// A.
// 使用一个或多个路径。匹配模式。
// None
// 指令必须紧跟在包含单个变量声明的行之前。
// 只有空行和'
// None
// 变量的类型必须是字符串类型或字节类型的切片，
// 或FS（或FS的别名）。
// None
// 例如：
// None
// 包服务器
// None
// 导入“嵌入”
// None
// None
// None
// None
// var content.FS
// None
// Go构建系统将识别指令并安排声明的变量
// （在上面的示例中，内容）将使用来自文件系统的匹配文件填充。
// None
// 这个
// 简洁，但也可以重复，以避免在有
// 许多模式。模式是相对于包目录进行解释的
// 包含源文件。路径分隔符是正斜杠，即使在
// Windows系统。模式不能包含“.”或“..”或空路径元素，
// 也不能以斜杠开始或结束。以匹配当前的所有内容
// 目录，请使用“*”而不是“.”。允许以空格命名文件的步骤
// 它们的名字、图案可以写为Go双引号或back引号
// 字符串文本。
// None
// 如果模式命名一个目录，则子树中以该目录为根的所有文件都将被删除
// 嵌入（递归），但名称以“.”或“\u1”开头的文件除外
// 排除在外。因此，上述示例中的变量几乎等于：
// None
// None
// None
// var content.FS
// None
// 区别在于，“image/*”嵌入了“image/.tempfile”，而“image”则没有。
// None
// 这个
// 取决于包是否希望将数据提供给其他包。
// 它只能与包范围内的全局变量一起使用，
// 不使用局部变量。
// None
// 模式不能与包模块外的文件匹配，例如“.git/*”或符号链接。
// 将忽略空目录的匹配项。在那之后，每一个模式在一个
// 必须至少匹配一个文件或非空目录。
// None
// 如果任何模式无效或具有无效匹配，则生成将失败。
// None
// 字符串和字节
// None
// 这个
// 这种模式只能匹配一个文件。字符串或[]字节初始化为
// 该文件的内容。
// None
// 这个
// 在不引用embed.FS的源文件中，使用空白导入（导入u“嵌入”）。
// None
// 文件系统
// None
// 对于嵌入单个文件，字符串或[]字节类型的变量通常是最好的。
// FS类型支持嵌入文件树，例如静态目录
// web服务器内容，如上面的示例所示。
// None
// FS实现io/FS包的FS接口，因此它可以与任何
// 了解文件系统，包括net/http、text/template和html/template。
// None
// 例如，给定上述示例中的内容变量，我们可以编写：
// None
// http.Handle（“/static/”，http.StripPrefix（“/static/”，http.FileServer（http.FS（content）））
// None
// template.ParseFS（内容“*.tmpl”）
// None
// 工具
// None
// 为了支持分析Go包的工具，在
// 在“go list”输出中可用。请参阅嵌入模式、测试MBedPatterns、，
// 和“go help list”输出中的XTestEmbedPatterns字段。
// None
package embed

import (
	"errors"
	"io"
	"io/fs"
	"time"
)

// FS是文件的只读集合，通常使用
// 在没有
// None
// FS是只读值，因此可以从多个goroutine中安全使用
// 同时，也可以安全地相互分配FS类型的值。
// None
// FS实现FS.FS，因此它可以与任何理解
// 文件系统接口，包括net/http、text/template和html/template。
// None
// 有关初始化FS的更多详细信息，请参阅软件包文档。
type FS struct {
	// 编译器知道这个结构的布局。
	// 请参阅cmd/compile/internal/staticdata的WriteEmbed。
	// None
	// 文件列表按名称排序，但不按简单的字符串比较排序。
	// 相反，每个文件的名称采用“dir/elem”或“dir/elem/”的形式。
	// 可选的尾部斜杠表示文件本身是一个目录。
	// 文件列表首先按目录排序（如果缺少目录，则将其视为“.”）
	// 然后按基数排序，因此此文件列表：
	// None
	// P
	// q/
	// q/r
	// q/s/
	// q/s/t
	// q/s/u
	// q/v
	// W
	// None
	// 实际分类为：
	// None
	// p#dir=。元素=p
	// q/#dir=。元素=q
	// w/#dir=。元素=w
	// q/r#dir=q elem=r
	// q/s/#dir=q elem=s
	// q/v#dir=q elem=v
	// q/s/t#dir=q/s elem=t
	// q/s/u#dir=q/s elem=u
	// None
	// 此顺序将目录内容集中在连续的部分中
	// 的列表，允许目录读取使用二进制搜索来查找
	// 相关条目的顺序。
	files *[]file
}

// 拆分将名称拆分为目录和元素，如中所述
// 在上面的FS结构中添加注释。isDir报告
// 最后一个尾随斜杠出现，表明名称是一个目录。
func split(name string) (dir, elem string, isDir bool) {
	if name[len(name)-1] == '/' {
		isDir = true
		name = name[:len(name)-1]
	}
	i := len(name) - 1
	for i >= 0 && name[i] != '/' {
		i--
	}
	if i < 0 {
		return ".", name, isDir
	}
	return name[:i], name[i+1:], isDir
}

// trimSlash从名称（如果存在）中修剪尾部斜杠，
// 返回可能缩短的名称。
func trimSlash(name string) string {
	if len(name) > 0 && name[len(name)-1] == '/' {
		return name[:len(name)-1]
	}
	return name
}

var (
	_ fs.ReadDirFS  = FS{}
	_ fs.ReadFileFS = FS{}
)

// 文件是FS中的单个文件。
// 它实现了fs.FileInfo和fs.DirEntry。
type file struct {
	// 编译器知道这个结构的布局。
	// 请参阅cmd/compile/internal/staticdata的WriteEmbed。
	name string
	data string
	hash [16]byte // 截断的SHA256散列
}

var (
	_ fs.FileInfo = (*file)(nil)
	_ fs.DirEntry = (*file)(nil)
)

func (f *file) Name() string               { _, elem, _ := split(f.name); return elem }
func (f *file) Size() int64                { return int64(len(f.data)) }
func (f *file) ModTime() time.Time         { return time.Time{} }
func (f *file) IsDir() bool                { _, _, isDir := split(f.name); return isDir }
func (f *file) Sys() interface{}           { return nil }
func (f *file) Type() fs.FileMode          { return f.Mode().Type() }
func (f *file) Info() (fs.FileInfo, error) { return f, nil }

func (f *file) Mode() fs.FileMode {
	if f.IsDir() {
		return fs.ModeDir | 0555
	}
	return 0444
}

// dotFile是根目录的文件，
// 从FS中的文件列表中省略。
var dotFile = &file{name: "./"}

// 查找返回命名文件，如果不存在，则返回nil。
func (f FS) lookup(name string) *file {
	if !fs.ValidPath(name) {
		// 编译器不应发出具有无效名称的文件，
		// 因此，严格来说，该检查不是必需的（如果名称无效，
		// 我们不应该在下面找到匹配项），但无论如何，这是一个很好的后盾。
		return nil
	}
	if name == "." {
		return dotFile
	}
	if f.files == nil {
		return nil
	}

	// 二进制搜索以查找名称在列表中的位置，
	// 然后检查名字是否在那个位置。
	dir, elem, _ := split(name)
	files := *f.files
	i := sortSearch(len(files), func(i int) bool {
		idir, ielem, _ := split(files[i].name)
		return idir > dir || idir == dir && ielem >= elem
	})
	if i < len(files) && trimSlash(files[i].name) == name {
		return &files[i]
	}
	return nil
}

// readDir返回与目录dir对应的文件列表。
func (f FS) readDir(dir string) []file {
	if f.files == nil {
		return nil
	}
	// 二进制搜索以查找目录在列表中的开始和结束位置
	// 然后返回列表的该部分。
	files := *f.files
	i := sortSearch(len(files), func(i int) bool {
		idir, _, _ := split(files[i].name)
		return idir >= dir
	})
	j := sortSearch(len(files), func(j int) bool {
		jdir, _, _ := split(files[j].name)
		return jdir > dir
	})
	return files[i:j]
}

// 打开打开命名文件进行读取，并将其作为fs.file返回。
func (f FS) Open(name string) (fs.File, error) {
	file := f.lookup(name)
	if file == nil {
		return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
	}
	if file.IsDir() {
		return &openDir{file, f.readDir(name), 0}, nil
	}
	return &openFile{file, 0}, nil
}

// ReadDir读取并返回整个命名目录。
func (f FS) ReadDir(name string) ([]fs.DirEntry, error) {
	file, err := f.Open(name)
	if err != nil {
		return nil, err
	}
	dir, ok := file.(*openDir)
	if !ok {
		return nil, &fs.PathError{Op: "read", Path: name, Err: errors.New("not a directory")}
	}
	list := make([]fs.DirEntry, len(dir.files))
	for i := range list {
		list[i] = &dir.files[i]
	}
	return list, nil
}

// ReadFile读取并返回命名文件的内容。
func (f FS) ReadFile(name string) ([]byte, error) {
	file, err := f.Open(name)
	if err != nil {
		return nil, err
	}
	ofile, ok := file.(*openFile)
	if !ok {
		return nil, &fs.PathError{Op: "read", Path: name, Err: errors.New("is a directory")}
	}
	return []byte(ofile.f.data), nil
}

// openFile是为读取而打开的常规文件。
type openFile struct {
	f      *file // 文件本身
	offset int64 // 当前读取偏移量
}

func (f *openFile) Close() error               { return nil }
func (f *openFile) Stat() (fs.FileInfo, error) { return f.f, nil }

func (f *openFile) Read(b []byte) (int, error) {
	if f.offset >= int64(len(f.f.data)) {
		return 0, io.EOF
	}
	if f.offset < 0 {
		return 0, &fs.PathError{Op: "read", Path: f.f.name, Err: fs.ErrInvalid}
	}
	n := copy(b, f.f.data[f.offset:])
	f.offset += int64(n)
	return n, nil
}

func (f *openFile) Seek(offset int64, whence int) (int64, error) {
	switch whence {
	case 0:
		// 偏移量+=0
	case 1:
		offset += f.offset
	case 2:
		offset += int64(len(f.f.data))
	}
	if offset < 0 || offset > int64(len(f.f.data)) {
		return 0, &fs.PathError{Op: "seek", Path: f.f.name, Err: fs.ErrInvalid}
	}
	f.offset = offset
	return offset, nil
}

// openDir是为读取而打开的目录。
type openDir struct {
	f      *file  // 目录文件本身
	files  []file // 目录内容
	offset int    // 读取偏移量，文件切片的索引
}

func (d *openDir) Close() error               { return nil }
func (d *openDir) Stat() (fs.FileInfo, error) { return d.f, nil }

func (d *openDir) Read([]byte) (int, error) {
	return 0, &fs.PathError{Op: "read", Path: d.f.name, Err: errors.New("is a directory")}
}

func (d *openDir) ReadDir(count int) ([]fs.DirEntry, error) {
	n := len(d.files) - d.offset
	if n == 0 {
		if count <= 0 {
			return nil, nil
		}
		return nil, io.EOF
	}
	if count > 0 && n > count {
		n = count
	}
	list := make([]fs.DirEntry, n)
	for i := range list {
		list[i] = &d.files[d.offset+i]
	}
	d.offset += n
	return list, nil
}

// sortSearch类似于sort.Search，避免导入。
func sortSearch(n int, f func(int) bool) int {
	// 定义f（-1）=false和f（n）=true。
	// 不变量：f（i-1）=假，f（j）=真。
	i, j := 0, n
	for i < j {
		h := int(uint(i+j) >> 1) // 在计算h时避免溢出
		// 我≤ h<j
		if !f(h) {
			i = h + 1 // 保持f（i-1）=假
		} else {
			j = h // 保持f（j）=真
		}
	}
	// i==j，f（i-1）==false，f（j）（=f（i））==true=>答案是i。
	return i
}
